﻿/*!
* mustache.js - Logic-less {{mustache}} templates with JavaScript
* http://github.com/janl/mustache.js
*/
var Mustache = (typeof module !== "undefined" && module.exports) || {};

(function (exports) {

    exports.name = "mustache.js";
    exports.version = "0.5.0-dev";
    exports.tags = ["{{", "}}"];
    exports.parse = parse;
    exports.compile = compile;
    exports.render = render;
    exports.clearCache = clearCache;

    // This is here for backwards compatibility with 0.4.x.
    exports.to_html = function (template, view, partials, send) {
        var result = render(template, view, partials);

        if (typeof send === "function") {
            send(result);
        } else {
            return result;
        }
    };

    var _toString = Object.prototype.toString;
    var _isArray = Array.isArray;
    var _forEach = Array.prototype.forEach;
    var _trim = String.prototype.trim;

    var isArray;
    if (_isArray) {
        isArray = _isArray;
    } else {
        isArray = function (obj) {
            return _toString.call(obj) === "[object Array]";
        };
    }

    var forEach;
    if (_forEach) {
        forEach = function (obj, callback, scope) {
            return _forEach.call(obj, callback, scope);
        };
    } else {
        forEach = function (obj, callback, scope) {
            for (var i = 0, len = obj.length; i < len; ++i) {
                callback.call(scope, obj[i], i, obj);
            }
        };
    }

    var spaceRe = /^\s*$/;

    function isWhitespace(string) {
        return spaceRe.test(string);
    }

    var trim;
    if (_trim) {
        trim = function (string) {
            return string == null ? "" : _trim.call(string);
        };
    } else {
        var trimLeft, trimRight;

        if (isWhitespace("\xA0")) {
            trimLeft = /^\s+/;
            trimRight = /\s+$/;
        } else {
            // IE doesn't match non-breaking spaces with \s, thanks jQuery.
            trimLeft = /^[\s\xA0]+/;
            trimRight = /[\s\xA0]+$/;
        }

        trim = function (string) {
            return string == null ? "" :
        String(string).replace(trimLeft, "").replace(trimRight, "");
        };
    }

    var escapeMap = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        '"': '&quot;',
        "'": '&#39;'
    };

    function escapeHTML(string) {
        return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
            return escapeMap[s] || s;
        });
    }

    /**
    * Adds the `template`, `line`, and `file` properties to the given error
    * object and alters the message to provide more useful debugging information.
    */
    function debug(e, template, line, file) {
        file = file || "<template>";

        var lines = template.split("\n"),
        start = Math.max(line - 3, 0),
        end = Math.min(lines.length, line + 3),
        context = lines.slice(start, end);

        var c;
        for (var i = 0, len = context.length; i < len; ++i) {
            c = i + start + 1;
            context[i] = (c === line ? " >> " : "    ") + context[i];
        }

        e.template = template;
        e.line = line;
        e.file = file;
        e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");

        return e;
    }

    /**
    * Looks up the value of the given `name` in the given context `stack`.
    */
    function lookup(name, stack, defaultValue) {
        if (name === ".") {
            return stack[stack.length - 1];
        }

        var names = name.split(".");
        var lastIndex = names.length - 1;
        var target = names[lastIndex];

        var value, context, i = stack.length, j, localStack;
        while (i) {
            localStack = stack.slice(0);
            context = stack[--i];

            j = 0;
            while (j < lastIndex) {
                context = context[names[j++]];

                if (context == null) {
                    break;
                }

                localStack.push(context);
            }

            if (context && typeof context === "object" && target in context) {
                value = context[target];
                break;
            }
        }

        // If the value is a function, call it in the current context.
        if (typeof value === "function") {
            value = value.call(localStack[localStack.length - 1]);
        }

        if (value == null) {
            return defaultValue;
        }

        return value;
    }

    function renderSection(name, stack, callback, inverted) {
        var buffer = "";
        var value = lookup(name, stack);

        if (inverted) {
            // From the spec: inverted sections may render text once based on the
            // inverse value of the key. That is, they will be rendered if the key
            // doesn't exist, is false, or is an empty list.
            if (value == null || value === false || (isArray(value) && value.length === 0)) {
                buffer += callback();
            }
        } else if (isArray(value)) {
            forEach(value, function (value) {
                stack.push(value);
                buffer += callback();
                stack.pop();
            });
        } else if (typeof value === "object") {
            stack.push(value);
            buffer += callback();
            stack.pop();
        } else if (typeof value === "function") {
            var scope = stack[stack.length - 1];
            var scopedRender = function (template) {
                return render(template, scope);
            };
            buffer += value.call(scope, callback(), scopedRender) || "";
        } else if (value) {
            buffer += callback();
        }

        return buffer;
    }

    /**
    * Parses the given `template` and returns the source of a function that,
    * with the proper arguments, will render the template. Recognized options
    * include the following:
    *
    *   - file     The name of the file the template comes from (displayed in
    *              error messages)
    *   - tags     An array of open and close tags the `template` uses. Defaults
    *              to the value of Mustache.tags
    *   - debug    Set `true` to log the body of the generated function to the
    *              console
    *   - space    Set `true` to preserve whitespace from lines that otherwise
    *              contain only a {{tag}}. Defaults to `false`
    */
    function parse(template, options) {
        options = options || {};

        var tags = options.tags || exports.tags,
        openTag = tags[0],
        closeTag = tags[tags.length - 1];

        var code = [
      'var buffer = "";', // output buffer
      "\nvar line = 1;", // keep track of source line number
      "\ntry {",
      '\nbuffer += "'
    ];

        var spaces = [],      // indices of whitespace in code on the current line
        hasTag = false,   // is there a {{tag}} on the current line?
        nonSpace = false; // is there a non-space char on the current line?

        // Strips all space characters from the code array for the current line
        // if there was a {{tag}} on it and otherwise only spaces.
        var stripSpace = function () {
            if (hasTag && !nonSpace && !options.space) {
                while (spaces.length) {
                    code.splice(spaces.pop(), 1);
                }
            } else {
                spaces = [];
            }

            hasTag = false;
            nonSpace = false;
        };

        var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;

        var setTags = function (source) {
            tags = trim(source).split(/\s+/);
            nextOpenTag = tags[0];
            nextCloseTag = tags[tags.length - 1];
        };

        var includePartial = function (source) {
            code.push(
        '";',
        updateLine,
        '\nvar partial = partials["' + trim(source) + '"];',
        '\nif (partial) {',
        '\n  buffer += render(partial,stack[stack.length - 1],partials);',
        '\n}',
        '\nbuffer += "'
      );
        };

        var openSection = function (source, inverted) {
            var name = trim(source);

            if (name === "") {
                throw debug(new Error("Section name may not be empty"), template, line, options.file);
            }

            sectionStack.push({ name: name, inverted: inverted });

            code.push(
        '";',
        updateLine,
        '\nvar name = "' + name + '";',
        '\nvar callback = (function () {',
        '\n  return function () {',
        '\n    var buffer = "";',
        '\nbuffer += "'
      );
        };

        var openInvertedSection = function (source) {
            openSection(source, true);
        };

        var closeSection = function (source) {
            var name = trim(source);
            var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;

            if (!openName || name != openName) {
                throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
            }

            var section = sectionStack.pop();

            code.push(
        '";',
        '\n    return buffer;',
        '\n  };',
        '\n})();'
      );

            if (section.inverted) {
                code.push("\nbuffer += renderSection(name,stack,callback,true);");
            } else {
                code.push("\nbuffer += renderSection(name,stack,callback);");
            }

            code.push('\nbuffer += "');
        };

        var sendPlain = function (source) {
            code.push(
        '";',
        updateLine,
        '\nbuffer += lookup("' + trim(source) + '",stack,"");',
        '\nbuffer += "'
      );
        };

        var sendEscaped = function (source) {
            code.push(
        '";',
        updateLine,
        '\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));',
        '\nbuffer += "'
      );
        };

        var line = 1, c, callback;
        for (var i = 0, len = template.length; i < len; ++i) {
            if (template.slice(i, i + openTag.length) === openTag) {
                i += openTag.length;
                c = template.substr(i, 1);
                updateLine = '\nline = ' + line + ';';
                nextOpenTag = openTag;
                nextCloseTag = closeTag;
                hasTag = true;

                switch (c) {
                    case "!": // comment
                        i++;
                        callback = null;
                        break;
                    case "=": // change open/close tags, e.g. {{=<% %>=}}
                        i++;
                        closeTag = "=" + closeTag;
                        callback = setTags;
                        break;
                    case ">": // include partial
                        i++;
                        callback = includePartial;
                        break;
                    case "#": // start section
                        i++;
                        callback = openSection;
                        break;
                    case "^": // start inverted section
                        i++;
                        callback = openInvertedSection;
                        break;
                    case "/": // end section
                        i++;
                        callback = closeSection;
                        break;
                    case "{": // plain variable
                        closeTag = "}" + closeTag;
                        // fall through
                    case "&": // plain variable
                        i++;
                        nonSpace = true;
                        callback = sendPlain;
                        break;
                    default: // escaped variable
                        nonSpace = true;
                        callback = sendEscaped;
                }

                var end = template.indexOf(closeTag, i);

                if (end === -1) {
                    throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
                }

                var source = template.substring(i, end);

                if (callback) {
                    callback(source);
                }

                // Maintain line count for \n in source.
                var n = 0;
                while (~(n = source.indexOf("\n", n))) {
                    line++;
                    n++;
                }

                i = end + closeTag.length - 1;
                openTag = nextOpenTag;
                closeTag = nextCloseTag;
            } else {
                c = template.substr(i, 1);

                switch (c) {
                    case '"':
                    case "\\":
                        nonSpace = true;
                        code.push("\\" + c);
                        break;
                    case "\r":
                        // Ignore carriage returns.
                        break;
                    case "\n":
                        spaces.push(code.length);
                        code.push("\\n");
                        stripSpace(); // Check for whitespace on the current line.
                        line++;
                        break;
                    default:
                        if (isWhitespace(c)) {
                            spaces.push(code.length);
                        } else {
                            nonSpace = true;
                        }

                        code.push(c);
                }
            }
        }

        if (sectionStack.length != 0) {
            throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
        }

        // Clean up any whitespace from a closing {{tag}} that was at the end
        // of the template without a trailing \n.
        stripSpace();

        code.push(
      '";',
      "\nreturn buffer;",
      "\n} catch (e) { throw {error: e, line: line}; }"
    );

        // Ignore `buffer += "";` statements.
        var body = code.join("").replace(/buffer \+= "";\n/g, "");

        if (options.debug) {
            if (typeof console != "undefined" && console.log) {
                console.log(body);
            } else if (typeof print === "function") {
                print(body);
            }
        }

        return body;
    }

    /**
    * Used by `compile` to generate a reusable function for the given `template`.
    */
    function _compile(template, options) {
        var args = "view,partials,stack,lookup,escapeHTML,renderSection,render";
        var body = parse(template, options);
        var fn = new Function(args, body);

        // This anonymous function wraps the generated function so we can do
        // argument coercion, setup some variables, and handle any errors
        // encountered while executing it.
        return function (view, partials) {
            partials = partials || {};

            var stack = [view]; // context stack

            try {
                return fn(view, partials, stack, lookup, escapeHTML, renderSection, render);
            } catch (e) {
                throw debug(e.error, template, e.line, options.file);
            }
        };
    }

    // Cache of pre-compiled templates.
    var _cache = {};

    /**
    * Clear the cache of compiled templates.
    */
    function clearCache() {
        _cache = {};
    }

    /**
    * Compiles the given `template` into a reusable function using the given
    * `options`. In addition to the options accepted by Mustache.parse,
    * recognized options include the following:
    *
    *   - cache    Set `false` to bypass any pre-compiled version of the given
    *              template. Otherwise, a given `template` string will be cached
    *              the first time it is parsed
    */
    function compile(template, options) {
        options = options || {};

        // Use a pre-compiled version from the cache if we have one.
        if (options.cache !== false) {
            if (!_cache[template]) {
                _cache[template] = _compile(template, options);
            }

            return _cache[template];
        }

        return _compile(template, options);
    }

    /**
    * High-level function that renders the given `template` using the given
    * `view` and `partials`. If you need to use any of the template options (see
    * `compile` above), you must compile in a separate step, and then call that
    * compiled function.
    */
    function render(template, view, partials) {
        return compile(template)(view, partials);
    }

})(Mustache);